package org.offsync.async

import java.io.IOException
import java.net.BindException
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.SocketAddress
import java.nio.channels.ClosedChannelException
import java.nio.channels.{SelectionKey => JavaSelectionKey}
import java.nio.channels.{ServerSocketChannel => JavaServerSocketChannel}
import java.nio.channels.SelectionKey
import java.nio.channels.ServerSocketChannel
import org.offsync.rich._
import org.offsync.util.AutoDisposed
import org.offsync.util.DisposableBase
import org.offsync.util.DisposedException
import org.offsync.util.Disposer

case class AsyncServerSocket(override val channel: JavaServerSocketChannel) extends AsyncSelectable(channel) {
  val accept = new AsyncInterest(this, SelectionKey.OP_ACCEPT)
  
  override val ops = List(accept)
  
  def fastAccept(fastAcceptCompletion: () => Unit)(task: (() => Option[AsyncSocket]) => Unit): Unit = {
    val optionAcceptedChannel = try {
      fastAcceptCompletion()

      Option(channel.accept())
    } catch {
      case e: ClosedChannelException => {
        task { () => throw new DisposedException(e) }
        return
      }
      case e: Exception => {
        task { () => throw e.wrap() }
        return
      }
    }
    
    for (acceptedChannel <- optionAcceptedChannel) {
      try {
        acceptedChannel.configureBlocking(false);
      } catch {
        case e: IOException => {
          try {
            acceptedChannel.close();
          } catch {
            case e: IOException => {
              e.printStackTrace(System.out);
              task { () => throw e.wrap() }
              return;
            }
          }
          
          task { () => throw e.wrap() }
          return
        }
      }
    }
    
    task { () => optionAcceptedChannel.map(AsyncSocket(_)) }
  }
  
  def accept(selector: AsyncSelector)(task: (() => Option[AsyncSocket]) => Unit) {
    fastAccept({() => }) { fastAcceptCompletion =>
      val optionSocket = try {
        fastAcceptCompletion()
      } catch {
        case e => {
          task { () => throw e.wrap() }
          return
        }
      }
      
      optionSocket match {
        case Some(socket) => {
          task { () => Some(socket) }
        }
        case None => {
          accept.onReady(selector) { acceptCompletion =>
            try {
              acceptCompletion()
              accept(selector)(task)
            } catch {
              case e => task { () => throw e.wrap() }
            }
          }
        }
      }
    }
  }
  
  @Override
  protected override def onDispose(): Unit = {
    try {
      channel.close()
    } catch {
      case e: Throwable => e.printStackTrace(System.err)
    } finally {
      super.onDispose()
    }
  }
}

object AsyncServerSocket {
  private def openImpl(disposer: Disposer, host: String, port: Int): (() => AsyncServerSocket) = {
    val scopedDisposer = new Disposer()
    try {
      val channel = scopedDisposer.closes(ServerSocketChannel.open())
      channel.configureBlocking(false)
      channel.socket().bind(new InetSocketAddress(host, port))
      scopedDisposer.cancel()
      val socket = disposer.disposes(new AsyncServerSocket(channel))
      return { () => socket }
    } catch {
      case e: IOException => { () => throw e.wrap() }
    } finally {
      scopedDisposer.dispose()
    }
  }

  private def openImpl(disposer: Disposer, host: String, ports: Seq[Int]): () => (AsyncServerSocket, Integer) = {
    val scopedDisposer = new Disposer()
    try {
      val channel = scopedDisposer.closes(ServerSocketChannel.open())
      
      channel.configureBlocking(false);
      
      var lastException: Option[IOException] = None
      
      if (ports.size > 0) {
        val moo = for (port <- ports) {
          try {
            channel.socket().bind(new InetSocketAddress(host, port));
            
            scopedDisposer.cancel()
            
            val socket = disposer.disposes(new AsyncServerSocket(channel))
            
            return { () => (socket, port) }
          } catch {
            case e: IOException => lastException = Some(e)
          }
        }
      } else {
        return { () => throw new IllegalArgumentException() }
      }
      
      return { () => throw lastException.get.wrap() }
    } catch {
      case e: IOException => return { () => throw e.wrap() }
    } finally {
      scopedDisposer.dispose()
    }
  }

  def open[T](host: String, port: Int)(callback: (() => AsyncServerSocket) => T): T = {
    val disposer = new Disposer()
    
    try {
       return callback(openImpl(disposer, host, port))
    } finally {
       disposer.dispose()
    }
  }
  
  def open(host: String, port: Int)(callback: (() => AsyncServerSocket) => Unit): Unit = {
    val disposer = new Disposer();
    
    try {
       callback(openImpl(disposer, host, port))
    } finally {
      disposer.dispose()
    }
  }
  
  def open(host: String, ports: Seq[Int])(callback: (() => (AsyncServerSocket, Integer)) => Unit): Unit = {
    val disposer = new Disposer();
    
    try {
      callback(openImpl(disposer, host, ports))
    } finally {
      disposer.dispose()
    }
  }
}
